/*******************************************************************************
 * Copyright (c) 2009 Kevin Bierhoff and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Kevin Bierhoff - initial API and implementation
 *******************************************************************************/
package com.googlecode.twelfdevtools.model;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;

import antlr.RecognitionException;
import antlr.TokenStreamException;

import com.googlecode.twelfdevtools.twelfast.ITwelfDeclaration;
import com.googlecode.twelfdevtools.twelfast.ITwelfIdentifier;
import com.googlecode.twelfdevtools.twelfast.TwelfAST;
import com.googlecode.twelfdevtools.util.UIUtil;

public class TwelfModel {
	
	private static TwelfModel instance;

	/** Gets the Twelf model */
	public static TwelfModel getModel() {
		if(instance == null) {
			instance = new TwelfModel();
		}
		return instance;
	}
	
	/**
	 * This model does not take file edits into account since the last saving. 
	 * @param twelfFile
	 * @return
	 * @see #getFileModel(ISourceViewer, IFile)
	 */
	public IElfFile getFileModel(IFile twelfFile) {
		// TODO caching
		IProject p = twelfFile.getProject();
		return new FileBasedFileModel(new TwelfProjectImpl(p), twelfFile);
	}
	
	/**
	 * Only use this if you're not planning on getting type bindings. 
	 * This one will only be able to bind variables defined in the given file.
	 * @param twelfViewer
	 * @return
	 * @see #getFileModel(ISourceViewer, IFile)
	 * @deprecated use {@link #getFileModel(IAnnotationModel)}
	 */
	@Deprecated
	public IElfFile getFileModel(ISourceViewer twelfViewer) {
		// TODO caching
		IAnnotationModel model = UIUtil.getAnnotationModel(twelfViewer);
		return new AnnotationBasedFileModel(model);
	}

	/**
	 * Only use this if you're not planning on getting type bindings. 
	 * This one will only be able to bind variables defined in the given file.
	 * @param model
	 * @return
	 * @see #getFileModel(ISourceViewer, IFile)
	 */
	public IElfFile getFileModel(IAnnotationModel model) {
		// TODO caching
		return new AnnotationBasedFileModel(model);
	}

	/** 
	 * This is the model to be used for editors.
	 * @deprecated Use {@link #getFileModel(IAnnotationModel, IFile)} instead 
	 */
	@Deprecated
	public IElfFile getFileModel(ISourceViewer twelfViewer, IFile twelfFile) {
		// TODO caching
		IAnnotationModel model = UIUtil.getAnnotationModel(twelfViewer);
		IProject p = twelfFile.getProject();
		return new AnnotationBasedFileModel(model, 
				new TwelfProjectImpl(p), twelfFile);
	}
	
	/** This is the model to be used for editors. */
	public IElfFile getFileModel(IAnnotationModel model, IFile twelfFile) {
		// TODO caching
		IProject p = twelfFile.getProject();
		return new AnnotationBasedFileModel(model, 
				new TwelfProjectImpl(p), twelfFile);
	}
	
	/**
	 * Finds the identifier region around the given offset, if any.
	 * @param twelfFile
	 * @param offset
	 * @return the identifier region around the given offset, or <code>null</code>.
	 */
	public IRegion findTwelfWord(IDocument doc, int offset) {
		int start = offset;
		int end = offset;
		
		try {
			while(start > 0 && isTwelfIdentifierChar(doc.getChar(start-1))) {
				--start;
			}
		} 
		catch (BadLocationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			while(end < doc.getLength() && isTwelfIdentifierChar(doc.getChar(end))) {
				++end;
			}
		} 
		catch (BadLocationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return new Region(start, end-start);
	}
	
	private boolean isTwelfIdentifierChar(char c) {
		return ((c >= 'A') && (c <= 'Z')) ||
				((c >= 'a') && (c <= 'z')) ||
				((c >= '0') && (c <= '9')) ||
				(c == '-') ||
				(c == '/') ||
				(c == '_') ||
				(c == '\'');
	}

	public ITwelfNamedElement getBinding(IElfFile fileModel, IDocument document, IRegion r) throws BadLocationException {
		// TODO make do with only the model or only the document
		String id = document.get(r.getOffset(), r.getLength()).trim();
		if("".equals(id))
			// nothing there
			return null;
		ITwelfConstant result = fileModel.getContext(r.getOffset()).getConstant(id);
		if(result != null)
			return result;
		// otherwise need to parse :(
		ITwelfDeclaration decl = findDeclaration(fileModel, document, r.getOffset());
		int line = document.getLineOfOffset(r.getOffset());
		int col = r.getOffset() - document.getLineOffset(line);
		ITwelfIdentifier node = TwelfAST.findIdentifier(decl, line, col);
		if(node == null || node.getBinding() == null)
			return null;
		else
			return node.getBinding().getModelElement();
	}
	
	public ITwelfDeclaration findDeclaration(IElfFile fileModel, IDocument document, int offset) {
		return findDeclaration(fileModel, document, offset, true);
	}

	private ITwelfDeclaration findDeclaration(IElfFile fileModel, 
			IDocument document, int offset,	boolean typed) {
		ITwelfConstant declModel = fileModel.getDeclaration(offset);
		if(declModel == null)
			return null;
		try {
			ITwelfDeclaration decl = parseDeclaration(document, 
					declModel.getDeclarationRange());
			// decl is null if the declaration couldn't be parsed in the document
			// this can happen when the document changed and the model is out of sync
			// so it thinks the declaration is in a different place than where it actually is
			if(decl != null && typed) {
				IDeclarationContext ctx = fileModel.getContext(offset);
				TwelfAST.bind(decl, declModel, ctx);
			}
			return decl;
		} 
		catch (RecognitionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		catch (TokenStreamException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		catch (BadLocationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	private ITwelfDeclaration parseDeclaration(IDocument twelfdoc, 
			IRegion range) throws BadLocationException, RecognitionException, TokenStreamException {
		String code = twelfdoc.get(range.getOffset(), range.getLength());
		if (! code.endsWith(".")) {
			// sigh, gotta go look for the end of this declaration
			int pos = code.indexOf('.');
			if (pos >= 0) {
				// declaration is shorter than expected...
				code = code.substring(0, pos + 1 /*include the dot*/);
			}
			else {
				// it's longer than expected so scan the rest of the document
				for (pos = range.getOffset() + range.getLength(); pos < twelfdoc.getLength(); ++pos) {
					if (twelfdoc.getChar(pos) == '.') {
						code = twelfdoc.get(range.getOffset(), pos - range.getOffset() + 1);
						break;
					}
				}
				if (pos == twelfdoc.getLength()) {
					// no dot to be found
					return null;
				}
			}
		}
		int line = twelfdoc.getLineOfOffset(range.getOffset());
		return TwelfAST.parseTwelfDeclaration(code, 
				line, range.getOffset() - twelfdoc.getLineOffset(line));
	}
	
	public static IFile getConfigFile(IProject twelfProject) {
		// TODO lookup config file path from project properties
		return twelfProject.getFile("sources.cfg");
	}

}
